package model.playingfield;

import java.awt.Color;
import java.io.Serializable;
import java.util.ArrayList;

import model.Player;

/**
 * This is the PlayingField. It consists of three areas, each
 * made up of spots: the starting areas, the circuit, and the runways.
 *
 * It has 4 Starting areas, each consisting of 4 Spots. This
 * is where each players Pieces will be placed at the onset
 * of the game.
 *
 * The main Circuit is a set of 40 Spots, leading around in a
 * connected pattern.
 * 		* 4 of these Spots are starting points, onto which each player
 * 		  moves his/her Pieces from the starting area to the circuit.
 * 		* 4 of these Spots are entry points to each players Runway.
 *
 * A players Runway is a set of 4 spots, leading up into the Nest.
 *
 * The Nest is merely an area out of which Pieces cannot be moved,
 * and needs no logical representation.
 *
 * Further, there are logical connections between all these three
 * classes of
 */
public class PlayingField implements Serializable{
    private static final long serialVersionUID = 1L;

    // Players and their information
    Player[] players;

    // Player pieces and their locations
    Integer[][] pieceIndex = new Integer[4][4];

    /*
     * Index over all spots:
     *   0 - 39  are the spots on the main circuit
     *   40 - 55 are the spots leading to the nest from a Spot
     *   56 - 71 are the starting areas
     */
    private ArrayList<Spot> spotIndex = new ArrayList<Spot>();

    // Index over where each players pieces are at the moment

    public PlayingField(Player[] players) {

        /*
         * Initiate the circuit:
         * 	 first initiate the Spots 	   	    (spots 0, 10, 20, 30)
         *   second initiate entry spots on the circuit (spots 1, 11, 21, 31)
         *   third initiate the Spots running up to the nest for each player
         *   fourth initiate the starting area Spots
         *   last initiate all other Spots, and tie them together
         */

        /*
         * Create initial initialization for all spots. Set them all to white,
         * without an owner. This will be changed later on.
         *
         * Also, make sure they all point where they should. This is pretty straight
         * forward.
         */
        this.players = players;

        for(int i = 0; i < 72; i++)
            spotIndex.add(i, new Spot(Color.WHITE, i));


        for(int i = 0; i < 40; i += 10) {
            spotIndex.remove(i);
            spotIndex.add(i, new BranchingSpot(Color.WHITE, i));
        }

        /*
         * Tie the circuit spots together (note that the last one, 39, should point
         * back to the first one, 0.
         */
        for(int i = 0; i < 39; i++)
            spotIndex.get(i).setNextSpot(spotIndex.get(i+1));
        spotIndex.get(39).setNextSpot(spotIndex.get(0));

        /*
         * Tie the runway spots together.
         */
        for(int i = 40; i <= 55; i+=4)
            for(int j = 0; j < 3; j++)
                spotIndex.get(i+j).setNextSpot(spotIndex.get(i+j+1));


        /*
         * Arrange the playingfield in a way that reflects all the participating
         * players. This will use the players ID in order to set stuff up.
         */

        for(Player player : players) {

            if(player == null)
                continue;

            /*
             * Initiate the branching Spots. These spots should have references both
             * to the next spot on the circuit, as well as the first spot on the
             * relevant players runway.
             *
             * After that, make sure that they are pointing where they should be
             * pointing.
             *
             * A little explanation of the maths:
             *
             * we find the index of the current players
             * BranchingSpot by multiplying that players ID with 10. This is because player
             * 0 has his/her BranchingSpot at 0, player 1 has his/her spot on 10, etc etc.
             *
             * We find the first Spot the BranchingSpot should point to by taking the players
             * ID, multiplying by 4, and then adding 40. This is because player 0s BranchingSpot
             * should point to spot 40, for player 1 it should be 44, etc.
             */
            int ID = player.getID();
            BranchingSpot workingSpot = (BranchingSpot)spotIndex.get(ID*10);

            workingSpot.setOwner(player);
            workingSpot.setNextSpot(spotIndex.get(workingSpot.getIndex() + 1));
            workingSpot.setNextRunway(spotIndex.get( (ID*4) + 40) );

            /*
             * Initiate the entry Spot to the circuit for the current player. This
             * is the Spot which the players pieces will first land on when moving out
             * of the starting area.
             */
            spotIndex.get( (ID*10 + 1) ).setOwner(player);

            //Branching spots:
            // 0 -> 40, 10 -> 44, 20 -> 48, 30 -> 52
            /*
             * Initiate Spots running up to the nest for each player (the runway Spots).
             * ^This is not true. - mruzuki
             * ^^ Indeed, I made a small mistake. Fixed now. - christopher
             */
            int startingIndex = (ID*4) + 56;
            for(int i = 0; i < 4; i++){
                //System.out.println("PlayingField, setting owner at: "+spotIndex.get(startingIndex + i).getIndex()+" to: " +player.getID());
                spotIndex.get(startingIndex + i).setOwner(player);
            }

            /*
             * Finally, initiate the starting Spots, and put the player pieces into
             * their starting positions. The starting position of a given piece is of
             * course in it's respective players starting location.
             *
             * Any spot in a starting location should point to the entry Spot for that
             * player, that is, the first spot on the circuit a Piece leaving the
             * starting area should land on.
             *
             * Along with setting this all up, set up the pieceIndex as well, which
             * merely contains indexes to where a given players pieces are.
             */

            int entrySpotIndex = (ID*10) + 1;
            for(int i = 0; i < 4; i++) {

                Spot currentSpot = spotIndex.get(startingIndex + i);

                currentSpot.setOwner(player);
                currentSpot.setOccupant(new Piece(player, i));
                currentSpot.setNextSpot(spotIndex.get(entrySpotIndex));

                pieceIndex[ID][i] = currentSpot.getIndex();
            }
        }
    }

    /**
     * Move the piece in the target location back to its starting area.
     * @param location
     */
    public void moveToStart(int location){

        Spot spot = spotIndex.get(location);
        Piece occupant = spot.getOccupant();

        if(occupant == null)
            return;

        int occupantID = occupant.getID();
        int ownerID = occupant.getOwner().getID();
        int newLocation = (ownerID * 4 + 56 + occupantID);

        spot.removeOccupant();
        spotIndex.get(newLocation).setOccupant(occupant);

        pieceIndex[ownerID][occupantID] = newLocation;
    };

    /**
     * Move a given players piece from its current location to a new one.
     *
     * @param player the player whose piece to move
     * @param index  index at which the piece resides
     * @param moves  how many moves should be made
     * @return the new index of the piece
     */
    public int movePiece(int from, int moves) {

        Piece occupant = spotIndex.get(from).getOccupant();
        Spot currentSpot = spotIndex.get(from);

        /*
         * Remove the Piece from the spot it currently occupies,
         * and move it forward for the correct number of moves.
         *
         * If the piece reaches its players runway strip in this
         * process, then leave the main circuit and enter the
         * runway.
         */

        currentSpot.removeOccupant();
        System.out.println(occupant);

        for(int i = 0; i < moves; i++) {

            // Have we reached a BranchingSpot?
            if(currentSpot instanceof BranchingSpot) {

                /*
                 * Does it belong to the current player? If yes, enter the runway.
                 * If not, just keep moving along the circuit.
                 */
                if(currentSpot.getOwner() != null){ //WARNING: SUSPECTED COPY+PASTE FROM ANOTHER METHOD  // LIES!!!1
                    if(currentSpot.getOwner().equals(occupant.getOwner())) {
                        currentSpot = ((BranchingSpot) currentSpot).getNextRunway();
                        continue;
                    }
                }
            }

            currentSpot = currentSpot.getNextSpot();

            /*
             * if we have reached a Spot that does not exist, simply return -1. This
             * should only happen if the piece has landed on the nest.
             */
            if(currentSpot == null) {
                pieceIndex[occupant.getOwner().getID()][occupant.getID()] = -1;
                System.out.println("\nSETTING NEW INDEX OF PIECE TO: " + pieceIndex[occupant.getOwner().getID()][occupant.getID()]);
                return -1;
            }

        }

        currentSpot.setOccupant(occupant);

        /*
         * Update the pieceIndex to contain the new index of the Spot which the Piece
         * is now located at.
         */
        pieceIndex[occupant.getOwner().getID()][occupant.getID()] = currentSpot.getIndex();
        System.out.println("\nSETTING NEW INDEX OF PIECE TO: " + pieceIndex[occupant.getOwner().getID()][occupant.getID()]);

        System.out.println("movePiece: " + currentSpot.getIndex());
        return currentSpot.getIndex();
    }

    public Integer[][] getPieceLocations() {

        return pieceIndex;
    }

    /** IMPLEMENTED FOR TEST
     * Method to set a player pieces at a position.
     * @param player The player
     * @param index His locations
     */
    //	public void setPlayerPieceAtLocation(Player player ,Integer index){
    //		pieceIndex[ID][0] = index;
    //	}

    /**
     * Checks if the their is players at field at the moment.
     * @return True if the there is players at the field.
     */
    public boolean hasPlayerAtField(){
        boolean hasPlayers = false;
        for(int i=0; i < pieceIndex.length - 1; i++){
            for(int k=0; k < pieceIndex.length - 1; k++){
                if(pieceIndex[i][k] != null){
                    hasPlayers = true;
                    return hasPlayers;
                }
            }
        }
        return hasPlayers;
    }



    /**
     * Checks if a given spot is occupied or not.
     * @param location
     * @return
     */
    public boolean isSpotOccupied(Integer location) {
        return spotIndex.get(location).isOccupied();
    }

    public int findDestinationSpot(Integer from, Integer moves) {

        Piece occupant = spotIndex.get(from).getOccupant();
        Spot currentSpot = spotIndex.get(from);

        //TODO Refactor this hack
        if(from > 55){
            return -2;
        }

        for(int i = 0; i < moves; i++) {

            // Have we reached a BranchingSpot? (and do we still have moves left after this one?)
            if(currentSpot instanceof BranchingSpot && i != moves - 1) {

                if(currentSpot.getOwner() != null){
                    if(currentSpot.getOwner().equals(occupant.getOwner())) {
                        currentSpot = ((BranchingSpot) currentSpot).getNextRunway();

                        continue;
                    }
                }
            }

            currentSpot = currentSpot.getNextSpot();
            if(currentSpot == null)
                return -1;
        }

        //This will throw nullpointer if the spot is in a runway
        // should be fixed now - christopher
        System.out.println("NEXT INDEX: "+currentSpot.getIndex());

        return currentSpot.getIndex();
    }

    /**
     * Returns the occupant of a given Spot. If no occupant is there,
     * this method should simply return null, and it is the duty of the
     * caller to check this result to avoid exceptions.
     * @param location
     * @return
     */
    public Integer whoOccupiesSpot(Integer location) {
        System.out.println("Spot occupied by: "+spotIndex.get(location).getOccupant().getOwner().getID());
        return spotIndex.get(location).getOccupant().getOwner().getID();
    }

    /**
     * Used for more manual moves, that is, moving one piece from one Spot
     * directly to another, regardless of any underlying rules.
     * @param from
     * @param to
     */
    public void moveSpecific(int from, int to) {

        Piece occupant = spotIndex.get(from).getOccupant();

        spotIndex.get(from).removeOccupant();
        spotIndex.get(to).setOccupant(occupant);

        pieceIndex[occupant.getOwner().getID()][occupant.getID()] = to;
    }

    public Spot getSpot(int index) {
        return spotIndex.get(index);
    }

    /**
     * Permanently remove a Piece from the game.
     * @param location
     */
    public void removePiece(int location) {

        Spot spot = spotIndex.get(location);
        Piece occupant = spot.getOccupant();

        spot.removeOccupant();

        pieceIndex[occupant.getOwner().getID()][occupant.getID()] = null;
    }
}
